package com.se.service;

import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.se.dao.mysql.ApplyDao;
import com.se.dao.mysql.UserDao;
import com.se.dao.redis.WxOpenRedisDao;
import com.se.domain.dto.wx.WxAccessTokenDTO;
import com.se.domain.dto.wx.WxUserAccessTokenDTO;
import com.se.domain.dto.wx.WxUserInfoDTO;
import com.se.domain.mysql.ActivityDO;
import com.se.enums.ResultCode;
import com.se.exception.ResultException;
import lombok.Data;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

@Slf4j
@Service
public class WxOpenService {



	private String appid = "wxa9b7160242f26ce7";

	private String secret = "b2118e6d268a8d34d7bba4ec5375755f";

	private String domain = "lingshuyi.xyz";
	
	@Getter
	private WxAccessTokenDTO accessTokenDTO;
	@Autowired
	private WxOpenRedisDao wxOpenRedisDao;
	@Autowired
	private WxDelayQueueManagers wxDelayQueueManagers;

	@Autowired
	private UserDao userDao;
	@Autowired
	private ApplyDao applyDao;
	
	@PostConstruct
	public void initAccessToken(){
		WxAccessTokenDTO accessTokenDTO = wxOpenRedisDao.getAccessToken();
		if(accessTokenDTO == null){
			this.refreshAccessToken();
		}else {
			if(System.currentTimeMillis() > accessTokenDTO.getExpiresTime()){
				this.refreshAccessToken();
			}else {
				this.putDelayQueue(accessTokenDTO);
				this.accessTokenDTO = accessTokenDTO;
			}
		}
		
		log.info("init access_token: " + accessTokenDTO);
	}
	
	public WxAccessTokenDTO refreshAccessToken(){
		String url = UrlBuilder.of("https://api.weixin.qq.com/cgi-bin/token")
				.addQuery("grant_type","client_credential")
				.addQuery("appid",this.appid)
				.addQuery("secret",this.secret)
				.build();
		String body = HttpRequest.get(url).execute().body();
		JSONObject jsonObject = JSONUtil.parseObj(body);
		log.info("refresh accessToken: "+jsonObject);
		this.accessTokenDTO = new WxAccessTokenDTO(jsonObject);
		
		this.wxOpenRedisDao.storeAccessToken(accessTokenDTO);
		this.putDelayQueue(accessTokenDTO);
		
		
		return this.accessTokenDTO;
	}
	
	private void putDelayQueue(WxAccessTokenDTO accessTokenDTO){
		this.wxDelayQueueManagers.put(
				new WxDelayQueueManagers.DelayTask(
						this::refreshAccessToken, accessTokenDTO.getExpiresTime()));
	}
	
	public WxUserAccessTokenDTO getUserAccessTokenByCode(String code) throws ResultException {
		String url = UrlBuilder.of("https://api.weixin.qq.com/sns/oauth2/access_token")
				.addQuery("appid",this.appid)
				.addQuery("secret",this.secret)
				.addQuery("code",code)
				.addQuery("grant_type","authorization_code")
				.build();
		String body = HttpRequest.get(url).execute().body();
		JSONObject jsonObject = JSONUtil.parseObj(body);
		this.checkErrCode(jsonObject);
		log.info("wx code: {}, get access_token: {}",code, jsonObject);
		return new WxUserAccessTokenDTO(jsonObject);
	}
	
	public WxUserAccessTokenDTO getAccessTokenByRefreshToken(String refreshToken) throws ResultException {
		String url = UrlBuilder.of("https://api.weixin.qq.com/sns/oauth2/refresh_token")
				.addQuery("appid",this.appid)
				.addQuery("grant_type","refresh_token")
				.addQuery("refresh_token",refreshToken)
				.build();
		String body = HttpRequest.get(url).execute().body();
		JSONObject jsonObject = JSONUtil.parseObj(body);
		this.checkErrCode(jsonObject);
		
		return new WxUserAccessTokenDTO(jsonObject);
	}
	
	public WxUserInfoDTO getUserInfo(String accessToken, String openId) throws ResultException {
		String url = UrlBuilder.of("https://api.weixin.qq.com/sns/userinfo")
				.addQuery("access_token",accessToken)
				.addQuery("openid",openId)
				.addQuery("lang","zh_CN")
				.build();
		String body = HttpRequest.get(url).execute().body();
		JSONObject jsonObject = JSONUtil.parseObj(body);
		this.checkErrCode(jsonObject);
		log.info("wx access_token:{} openid:{}, get user_info:{}",accessToken,openId,jsonObject);
		return new WxUserInfoDTO(jsonObject);
	}
	
	private void checkErrCode(JSONObject jsonObject) throws ResultException {
		Integer errCode = jsonObject.getInt("errcode");
		if(errCode != null && errCode != 0){
			log.error("微信请求出错:"+jsonObject);
			switch (errCode){
				case 40029:
					throw new ResultException(ResultCode.INVALID_CODE);
				default:
					throw new ResultException(ResultCode.FAIL);
			}
			
		}
	}
	
	private JSONObject valueAndColor(String value, String color){
		JSONObject jsonObject = new JSONObject();
		jsonObject.set("value", value);
		jsonObject.set("color", color);
		return jsonObject;
	}

	@Async("wxAsyncExecutor")
	public void notifyActCancel(ActivityDO activityDO){

		Long qnId = activityDO.getQnId();
		List<Long> userIds = applyDao.listUserIdByQnId(qnId);
		List<String> openIdList = userDao.listOpenIdBuUserIds(userIds);

		JSONObject jsonObject = new JSONObject();

		jsonObject.set("template_id", "9c3s486mNrZDIQouk_zxyY5sWARPMFjApD-7MQLiqlg");
		jsonObject.set("topcolor", "#FF0000");
		jsonObject.set("url", "http://lingshuyi.xyz");

		JSONObject data = new JSONObject();
		data.set("title", this.valueAndColor(activityDO.getTitle(), "#173177"));
		data.set("startTime",this.valueAndColor(activityDO.getStartTime(), "#173177"));
		data.set("endTime",this.valueAndColor(activityDO.getEndTime(), "#173177"));
		data.set("place",this.valueAndColor(activityDO.getPlace(), "#173177"));

		jsonObject.set("data", data);
		String url = UrlBuilder.of("https://api.weixin.qq.com/cgi-bin/message/template/send")
				.addQuery("access_token",this.accessTokenDTO.getAccessToken())
				.build();

		for (String openId : openIdList) {
			this.executeWxNotify(jsonObject, url, openId);
		}
	}
	@Async("wxAsyncExecutor")
	public void executeWxNotify(JSONObject jsonObject, String url, String openId) {
		jsonObject.set("touser", openId);
		String body = HttpRequest.post(url).body(jsonObject.toString()).execute().body();
		log.info("notify user: {}, response: {}", openId, body);
	}

	public void setWxButton(){
		//        String findUrl = UrlBuilder.of("https://lingshuyi.xyz/auth/login").addQuery("from", "https://lingshuyi.xyz/index").build();
		
//		String findUrl = "https://lingshuyi.xyz/auth/login?from="+ URLEncoder.encode("https://lingshuyi.xyz/index", StandardCharsets.UTF_8);
//		WxButton findButton = WxButton.viewButton("发现", findUrl);
//        String shuhuiUrl = UrlBuilder.of("https://lingshuyi.xyz/auth/login").addQuery("from", "https://lingshuyi.xyz/shuhui").build();
//		String shuhuiUrl = "https://lingshuyi.xyz/auth/login?from=" + URLEncoder.encode("https://lingshuyi.xyz/shuhui/2", StandardCharsets.UTF_8);
//		WxButton shuhuiButton = WxButton.viewButton("书荟", shuhuiUrl);
//        String userUrl = UrlBuilder.of("https://lingshuyi.xyz/auth/login").addQuery("from", "https://lingshuyi.xyz/user").build();
//		String userUrl = "https://lingshuyi.xyz/auth/login?from=" + URLEncoder.encode("https://lingshuyi.xyz/user", StandardCharsets.UTF_8);
//		WxButton userButton = WxButton.viewButton("我的", userUrl);
		List<WxButton> list = new ArrayList<>();

		String indexUrl = "http://lingshuyi.xyz";
		WxButton indexButton = WxButton.viewButton("首页", indexUrl);
		list.add(indexButton);

		String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token="+this.accessTokenDTO.getAccessToken();
		JSONObject body = JSONUtil.createObj();
		body.set("button", list);
		String resBody = HttpRequest.post(url).body(body.toString()).execute().body();
		
		log.info("set wx menu button: {} with response: {}", body, resBody);
	}
	
	@Data
	public static class WxButton{
		public static class WxButtonType{
			public static final String CLICK = "click";
			public static final String VIEW = "view";
		}
		private String type;
		private String name;
		private String key;
		private String url;
		private WxButton(){}
		
		public static WxButton clickButton(String name, String key){
			WxButton wxButton = new WxButton();
			wxButton.setType(WxButtonType.CLICK);
			wxButton.setName(name);
			wxButton.setKey(key);
			return wxButton;
		}
		public static WxButton viewButton(String name, String url){
			WxButton wxButton = new WxButton();
			wxButton.setType(WxButtonType.VIEW);
			wxButton.setName(name);
			wxButton.setUrl(url);
			return wxButton;
		}
	}
	
	@Component
	@Slf4j
	public static class WxDelayQueueManagers implements CommandLineRunner {
		@ToString
		public static class DelayTask implements Delayed {
			final private Supplier supplier;
			final private long expireTime;
			
			/**
			 * 构造延时任务
			 * @param supplier 任务函数
			 * @param expireTime 任务延时时间，指定时间执行（ms）
			 */
			
			private DelayTask(Supplier supplier, long expireTime) {
				super();
				this.supplier = supplier;
				this.expireTime = expireTime;
			}
			
			@Override
			public long getDelay(TimeUnit unit) {
				return unit.convert(this.expireTime - System.currentTimeMillis(), unit);
			}
			
			@Override
			public int compareTo(Delayed o) {
				long delta = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
				return (int) delta;
			}
		}
		
		private final DelayQueue<DelayTask> delayQueue = new DelayQueue<>();
		
		/**
		 * 加入到延时队列中
		 * @param task
		 */
		public void put(DelayTask task) {
			log.info("加入延时任务：{}", task);
			delayQueue.put(task);
		}
		/**
		 * 取消延时任务
		 * @param task
		 * @return
		 */
		public boolean remove(DelayTask task) {
			log.info("取消延时任务：{}", task);
			return delayQueue.remove(task);
		}
		
		@Override
		public void run(String... args) throws Exception {
			log.info("初始化延时队列");
			Executors.newSingleThreadExecutor().execute(new Thread(this::executeThread));
		}
		/**
		 * 延时任务执行线程
		 */
		private void executeThread() {
			while (true) {
				try {
					DelayTask task = delayQueue.take();
					processTask(task);
				} catch (InterruptedException e) {
					e.printStackTrace();
					break;
				}
			}
		}
		/**
		 * 内部执行延时任务
		 * @param task
		 */
		private void processTask(DelayTask task) {
			log.info("执行延时任务");
			task.supplier.get();
		}
	}
}
